package pl.elka.pszt.model.literal;

import java.util.List;
import java.util.ListIterator;

import pl.elka.pszt.model.argument.Argument;

/**
 * Reprezentuje pojedynczy literal identyfikowany poprzez swoj predykat. Literal
 * zawiera tez informacje czy jest zanegowany oraz liste argumentow.
 * 
 * @author Andrzej Makarewicz
 * 
 */
public class Literal {
	
	private final Predicat predicat;
	private final boolean isPositive;
	private final List<Argument> arguments;
	private final LiteralHelper helper;
	
	public Literal(Predicat predicat, boolean isPositive, List<Argument> arguments, LiteralHelper helper) {
		if (predicat == null || arguments == null) throw new NullPointerException();
		
		this.predicat = predicat;
		this.isPositive = isPositive;
		this.arguments = arguments;
		this.helper = helper;
	}
	
	/**
	 * 
	 * @return przypisany predykat
	 */
	public Predicat getPredicat() {
		return predicat;
	}
	
	/**
	 * 
	 * @return czy predykat nie jest zanegowany
	 */
	public boolean isPositive() {
		return isPositive;
	}
	
	/**
	 * 
	 * @return czy predykat jest zanegowany
	 */
	public boolean isNegative() {
		return !isPositive;
	}
	
	/**
	 * 
	 * @return argumenty literalu
	 */
	public List<Argument> getArguments() {
		return arguments;
	}
	
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((arguments == null) ? 0 : arguments.hashCode());
		result = prime * result + (isPositive ? 1231 : 1237);
		result = prime * result + ((predicat == null) ? 0 : predicat.hashCode());
		return result;
	}
	
	/**
	 * 
	 * @return identyfikator bez brania pod uwage argumentow
	 */
	public int hashCodeSimplified() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (isPositive ? 1231 : 1237);
		result = prime * result + ((predicat == null) ? 0 : predicat.hashCode());
		return result;
	}
	
	@Override
	public boolean equals(Object obj) {
		if (this == obj) return true;
		if (obj == null) return false;
		if (getClass() != obj.getClass()) return false;
		Literal other = (Literal) obj;
		if (isPositive != other.isPositive) return false;
		if (predicat == null) {
			if (other.predicat != null) return false;
		} else if (!predicat.equals(other.predicat)) return false;
		if (arguments == null) {
			if (other.arguments != null) return false;
		} else if (!arguments.equals(other.arguments)) return false;
		return true;
	}
	
	/**
	 * Porownanie bez brania pod uwage argumentow
	 * 
	 * @param obj
	 * @return
	 */
	public boolean equalsSimplified(Object obj) {
		if (this == obj) return true;
		if (obj == null) return false;
		if (getClass() != obj.getClass()) return false;
		Literal other = (Literal) obj;
		if (isPositive != other.isPositive) return false;
		if (predicat == null) {
			if (other.predicat != null) return false;
		} else if (!predicat.equals(other.predicat)) return false;
		return true;
	}
	
	/**
	 * 
	 * @param other
	 * @return czy literaly mozna dopasowac
	 */
	public boolean matches(Literal other) {
		if (other == null) return false;
		return matches(other, other.arguments);
	}
	
	/**
	 * 
	 * @param other
	 * @param args
	 * @return czy literaly mozna dopasowac, biorac pod uwage inna liste
	 *         argumentow
	 */
	public boolean matches(Literal other, List<? extends Argument> args) {
		if (other == null) return false;
		if (isPositive != other.isPositive) return false;
		if (predicat == null) {
			if (other.predicat != null) return false;
		} else if (!predicat.equals(other.predicat)) return false;
		if (!argumentsMatch(args)) return false;
		return true;
	}
	
	/**
	 * 
	 * @param other
	 * @return czy lista argumentow pozwala na dopasowanie
	 */
	public boolean argumentsMatch(List<? extends Argument> other) {
		if (other == arguments) return true;
		if (arguments == null) return other == null;
		
		ListIterator<? extends Argument> e1 = arguments.listIterator();
		ListIterator<? extends Argument> e2 = other.listIterator();
		while (e1.hasNext() && e2.hasNext()) {
			Argument o1 = e1.next();
			Argument o2 = e2.next();
			if (!(o1 == null ? o2 == null : o1.matches(o2))) return false;
		}
		return !(e1.hasNext() || e2.hasNext());
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		if (isNegative()) sb.append("-");
		sb.append(helper.getPredicatName(predicat));
		sb.append("(");
		for (Argument arg : arguments) {
			sb.append(arg.toString());
			sb.append(", ");
		}
		sb.replace(sb.length() - 2, sb.length(), "");
		sb.append(")");
		
		return sb.toString();
	}
	
}
